package com.redhat.auditor.client;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FilenameUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import com.redhat.auditor.helper.console;
import com.redhat.auditor.helper.Utils.StringSequence;
import com.redhat.auditor.webdiff.WebDiff;

/**
 * Auditor Web Application servlet implementation class {@code CompareServlet}.
 * 
 * @version 0.01
 * @author Trungtin Ton
 */
@WebServlet(description = "Servlet that handles all requests to the native WebDiff interface of Auditor.", urlPatterns = { "/CompareServlet" })
public class CompareServlet extends HttpServlet {
	/**
	 * Default generated serialization universal identifier for this class.
	 */
	private static final long serialVersionUID = 1L;

	/**
	 * Default constructor.
	 */
	public CompareServlet() {

	}

	/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
	 *      response)
	 */
	protected void doGet(final HttpServletRequest request,
			final HttpServletResponse response) {
		try {
			doRequest(request, response);
		} catch (final Exception e) {
			// IOExceptions die quietly from the server-side, meaning that the
			// client will never receive any sort of response or feedback if the
			// server cannot correctly communicate with the client. The client
			// application is therefore relied upon to address any no-response
			// timeout situation that may result if the server cannot at all
			// communicate with the client.
			e.printStackTrace();
		}
	}

	/**
	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse
	 *      response)
	 */
	protected void doPost(final HttpServletRequest request,
			final HttpServletResponse response) {
		try {
			doRequest(request, response);
		} catch (final Exception e) {
			// IOExceptions die quietly from the server-side, meaning that the
			// client will never receive any sort of response or feedback if the
			// server cannot correctly communicate with the client. The client
			// application is therefore relied upon to address any no-response
			// timeout situation that may result if the server cannot at all
			// communicate with the client.
			e.printStackTrace();
		}
	}

	/**
	 * Fulfills the given WebDiff HTTP request and commits a tailored HTTP
	 * response to the client with the response data, accordingly. If the
	 * request function cannot complete the operation successfully, then it will
	 * return a HTTP error code response to the client. If it has trouble
	 * communicating with the server, then it will fail and throw an exception.
	 * 
	 * @param request
	 *            the HTTP Servlet request object that has been sent to the
	 *            server by the client attempting to invoke a WebDiff comparison
	 *            operation. This request should contain the necessary
	 *            parameters to conduct the operation, in accordance with the
	 *            WebDiff API.
	 * @param response
	 *            the empty HTTP Servlet response object that will be populated
	 *            with the results of this operation to fulfill the given
	 *            client-side request.
	 * @throws IOException
	 *             if the request handler can find no way of returning any
	 *             response (even a HTTP error response code) back to the client
	 *             that issued the request.
	 * @throws ServletException
	 */
	private void doRequest(final HttpServletRequest request,
			final HttpServletResponse response) throws IOException,
			ServletException {

		InputStream source = null, dest = null;

		// TODO implement validation and escaping on the request parameters.

		try {
			@SuppressWarnings("unchecked")
			final List<FileItem> items = new ServletFileUpload(
					new DiskFileItemFactory()).parseRequest(request);
			for (final FileItem item : items) {
				if (item.isFormField()) {
					// Process regular form field (input
					// type="text|radio|checkbox|etc", select, etc).
					final String fieldname = item.getFieldName();
					final String fieldvalue = item.getString();
					console.log(fieldname + ": " + fieldvalue);
				} else {
					// Process form file field (input type="file").
					final InputStream filecontent = item.getInputStream();
					final String fieldname = item.getFieldName();
					final String filename = FilenameUtils.getName(item
							.getName());
					console.log(fieldname + ": " + filename);
					if (fieldname.equals("source")) {
						source = filecontent;
					} else if (fieldname.equals("dest")) {
						dest = filecontent;
					}
				}
			}
		} catch (final FileUploadException e) {
			throw new ServletException("Cannot parse multipart request.", e);
		}
		try {
			final ArrayList<Object> args = new ArrayList<Object>(
					Arrays.asList((new Object[] { source, dest })));

			// Grab CSS files linked to the "destination" document under review
			args.add(getCSSLinksFrom(null));

			// Set the response and execute the comparison (directly to client)
			final PrintWriter w = response.getWriter();
			response.setContentType("text/html");
			WebDiff.compare(source, dest, w, null);
			response.setStatus(HttpServletResponse.SC_OK);
			w.flush();

		} catch (final IOException e) {
			response.sendError(HttpServletResponse.SC_BAD_REQUEST);
			e.printStackTrace();

		} finally {
			response.getWriter().close();
		}
	}

	/**
	 * Grabs the CSS files referenced from the links contained within the HTML
	 * document at the specified URL.
	 * 
	 * @param url
	 *            the URL of the HTML document from which to scrape links for
	 *            CSS files.
	 * @throws IOException
	 *             if an error occurs preventing a successful connection to the
	 *             online resource, and therefore access to the specified HTML
	 *             document.
	 */
	private static String getCSSLinksFrom(final String url) throws IOException {

		Elements links = getDocFrom(url).getElementsByTag("link");
		links = links.select("[type=text/css]");
		if (links.size() > 0) {
			final StringSequence css = new StringSequence(links.size());
			for (final Element link : links) {
				if (link.attr("media") == "screen"
						|| link.attr("media") != null) {
					css.add(link.absUrl("href"));
				}
			}
			return "--css=" + css.join(";");
		} else
			return null;
	}

	/**
	 * Retrieves a W3C-compliant JSoup Document Object from the contents of the
	 * given URL.
	 * 
	 * @param url
	 *            the URL from which to retrieve a Document object.
	 * @return a W3C-compliant {@code Document} object containing the contents
	 *         located at the given URL.
	 * @throws IOException
	 *             if the given URL cannot be accessed.
	 */
	private static Document getDocFrom(final String url) throws IOException {
		final Document doc;
		if (url.startsWith("http://")) {
			doc = Jsoup.connect(url).get();
		} else {
			doc = Jsoup.parse(new File(url), "UTF-8");
		}
		return doc;
	}

	/**
	 * Utility method for testing purposes. Reveals all of the request
	 * parameters and the associated values from a given HTTP request object.
	 * 
	 * @param request
	 *            the HTTP Servlet Request object whose parameters are to be
	 *            identified and displayed, if any.
	 */
	protected static void viewParameters(final HttpServletRequest request) {
		final Enumeration<String> parameterNames = request.getParameterNames();
		final Map<String, String[]> parameterMap = request.getParameterMap();
		String parameterName;
		String[] parameterValues;
		// Read all of the parameter values for each parameter name to console.
		while (parameterNames.hasMoreElements()) {
			parameterName = parameterNames.nextElement();
			parameterValues = parameterMap.get(parameterName);
			console.log(parameterName + ": " + (Arrays.asList(parameterValues)));
		}
	}

}
